export const createLoop = () => ({
    timer: {},
    map: {},
    register(key, fn, conf = {}) {
        if (this.map[key]) {
            return console.log(`[${key}] exist, please stop prev [${key}]`);
        }

        if (typeof fn !== 'function') {
            throw new TypeError('loop.register expects a function');
        }

        const mergedConf = {
            rid: Math.random(),
            inter: 5e3,
            retry: false,
            check: false,
            ...conf,
        };

        this.unregister(key);

        this.map[key] = {
            fn,
            count: {
                total: 0,
                success: 0,
                error: 0
            },

            conf: mergedConf,
        };

        this.execute(key, mergedConf.rid);
    },
    execute(key, rid) {
        const origin = this.map[key];
        if (!origin || rid !== origin.conf.rid) return;

        const { fn, conf } = origin;
        origin.count.total += 1

        const schedule = () => {
            this.clearTimer(key);
            const delay = Math.max(100, Number(conf.inter) || 0);
            this.timer[key] = setTimeout(() => this.execute(key, rid), delay);
        };

        const handleError = async (err) => {
            origin.count.error += 1
            if (conf.retry) {
                const catchRes = await conf.catch?.(err);
                if (catchRes) return;
                schedule();
            } else {
                this.unregister(key);
            }
        };

        const handleNext = async (res) => {
            origin.count.success += 1
            if (conf.check) {
                const thenRes = await conf.then?.(res);
                if (thenRes) return;
                return schedule();
            }

            this.unregister(key);
        };

        try {
            const res = fn();
            if (typeof res?.then === 'function') {
                res.then(handleNext).catch(handleError);
            } else {
                handleNext(res);
            }
        } catch (err) {
            handleError(err);
        }
    },
    retry(key, fn, conf = {}) {
        this.register(key, fn, { ...conf, check: false, retry: true });
    },
    check(key, fn, conf = {}) {
        this.register(key, fn, { ...conf, retry: false, check: true });
    },
    loop(key, fn, conf = {}) {
        this.register(key, fn, { ...conf, retry: true, check: true });
    },
    unregister(key) {
        if (this.map[key]) {
            delete this.map[key];
        }

        this.clearTimer(key);
    },
    clearTimer(key) {
        const timerId = this.timer[key];
        if (!timerId) return;

        clearTimeout(timerId);
        delete this.timer[key];
    },
});
